/*
 ------------------------------------------------------------------------------
	sdlmad - a test program for using mad library with SDL on Win32
	and Linux.

	Copyright (C) 2011 Pierre Veber
	
	This program is free software; you can redistribute it and/or modify
	it under the terms of the GNU General Public License as published by
	the Free Software Foundation; either version 3 of the License, or
	(at your option) any later version.
	
	This program is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU General Public License for more details.
	
	You should have received a copy of the GNU General Public License
	along with this program; if not, write to the Free Software Foundation,
	Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
------------------------------------------------------------------------------
*/

#include "stdafx.h"

#define READ_BUF_SIZE	8192
static unsigned char _read_buf[READ_BUF_SIZE];

void send_message(decode_thread_data_t* pData, int msg)
{
	if (pData->msg == DECODE_MSG_NONE)
	{
		pData->msg = msg;
		while (pData->msg != 0)
		{
			usleep(100000);
		}
	}
}

int get_message(decode_thread_data_t* pData)
{
	int ret = pData->msg;
	pData->msg = DECODE_MSG_NONE;
	return ret;
}

/*
 * This is the input callback. The purpose of this callback is to (re)fill
 * the stream buffer which is to be decoded. In this example, an entire file
 * has been mapped into memory, so we just call mad_stream_buffer() with the
 * address and length of the mapping. When this callback is called a second
 * time, we are finished decoding.
 */

enum mad_flow fMadInputCallback(void *pData, struct mad_stream *pStream)
{
	size_t read, to_read = READ_BUF_SIZE;
	decode_thread_data_t* pDecodeThreadData = (decode_thread_data_t*)pData;
	
	// check for messages
	if (get_message(pDecodeThreadData) == DECODE_MSG_STOP)
	{
		return MAD_FLOW_STOP;
	}
	
	// move remaining frames to beginning of buffer
	if (pStream->next_frame != NULL)
	{
		to_read = pStream->next_frame - _read_buf;
		memmove(_read_buf, pStream->next_frame, READ_BUF_SIZE - to_read);
	}
	read = fread(_read_buf + (READ_BUF_SIZE - to_read), 1, to_read, pDecodeThreadData->fp);
	if (read + READ_BUF_SIZE - to_read > 0)
	{
		mad_stream_buffer(
						pStream,
						(const unsigned char*)_read_buf,
						read + READ_BUF_SIZE - to_read
						);
		if (read != 0)
		{
			return MAD_FLOW_CONTINUE;
		}
	}
	return MAD_FLOW_STOP;
}

/*
 * This is the output callback function. It is called after each frame of
 * MPEG audio data has been completely decoded. The purpose of this callback
 * is to output (or play) the decoded PCM audio.
 */

static enum mad_flow fMadOutputCallback(void* pData, struct mad_header const *pHeader, struct mad_pcm *pPcm)
{
	char _buf[4];
	unsigned int nchannels, nsamples;
	mad_fixed_t const *left_ch, *right_ch;
	signed int sample;
	int rc;

	decode_thread_data_t* pDecodeThreadData = (decode_thread_data_t*)pData;

	nchannels = pPcm->channels;
	nsamples  = pPcm->length;

	left_ch   = pPcm->samples[0];
	right_ch  = pPcm->samples[1];
  
	while (nsamples--)
	{
		/* output sample(s) in 16-bit signed little-endian PCM */
		sample = scale(*left_ch++);
		_buf[0] = (sample >> 0) & 0xff;
		_buf[1] = (sample >> 8) & 0xff;
		sample = scale(*right_ch++);
		_buf[2] = (sample >> 0) & 0xff;
		_buf[3] = (sample >> 8) & 0xff;

		rc = waveWriteIn(&(pDecodeThreadData->audio), _buf, 1);
		/*if (rc != 1)
		{
			fprintf(stderr, "short write, write %d frames\n", rc);
		}*/
	}
	
	return MAD_FLOW_CONTINUE;
}

/*
 * This is the error callback function. It is called whenever a decoding
 * error occurs. The error is indicated by stream->error; the list of
 * possible MAD_ERROR_* errors can be found in the mad.h (or
 * libmad/stream.h) header file.
 */

static
enum mad_flow fMadErrorCallback(void* pData, struct mad_stream* pStream, struct mad_frame* pFrame)
{
	TRACE("decoding error 0x%04x (%s)\n", pStream->error, mad_stream_errorstr(pStream));

	/* return MAD_FLOW_BREAK here to stop decoding (and propagate an error) */

	return MAD_FLOW_CONTINUE;
}

void* fDecodeThread(void* lpParameter)
{
	int result;
	decode_thread_data_t* pDecodeThreadData = (decode_thread_data_t*)lpParameter;

	pDecodeThreadData->status = DECODE_STATUS_DECODING;

	/* configure input, output, and error functions */
	mad_decoder_init(
		&pDecodeThreadData->decoder,
		pDecodeThreadData,
        fMadInputCallback,
        0 /* header */,
        0 /* filter */,
        fMadOutputCallback,
        fMadErrorCallback,
        0 /* message */
        );

	/* start decoding */
	result = mad_decoder_run(&pDecodeThreadData->decoder, MAD_DECODER_MODE_SYNC);

	/* release the decoder */
	mad_decoder_finish(&pDecodeThreadData->decoder);
	
	/* change status */
	pDecodeThreadData->status = DECODE_STATUS_FINISHED;

	return NULL;
}

decode_thread_data_t* madDecode(audio_data_t* pAudio, const char* pszFile)
{
	decode_thread_data_t* pDecodeThreadData;
	
	pDecodeThreadData = (decode_thread_data_t*)malloc(sizeof(decode_thread_data_t));
	memset(pDecodeThreadData, 0, sizeof(decode_thread_data_t));

	pDecodeThreadData->fp = NULL;
	pDecodeThreadData->szFilePath = _strdup(pszFile);
	memcpy(&(pDecodeThreadData->audio), pAudio, sizeof(audio_data_t));

	/* open the file for reading */
	pDecodeThreadData->fp = fopen(pszFile, "rb");
	if (pDecodeThreadData->fp == NULL)
	{
		TRACE("Unable to fd open: %s !\n", pszFile);
		goto cleanup;
	}

	/* prepare audio */
	waveReset(&(pDecodeThreadData->audio));

	/* init of decode thread */
	if (pthread_create(&(pDecodeThreadData->tDecodeThread), NULL, fDecodeThread, pDecodeThreadData) != 0)
	{
		TRACE("Error creating decode thread!\n");
		goto cleanup;
	}
	
cleanup:
	return pDecodeThreadData;
}

int madStop(decode_thread_data_t* pDecodeThreadData)
{
	void* pDecodeThreadExit;

	/* stop decode thread */
	if ( pDecodeThreadData->status == DECODE_STATUS_DECODING )
	{
		TRACE("sending message for thread stop...\n");
		send_message(pDecodeThreadData, DECODE_MSG_STOP);
	}

	TRACE("waiting for Decode thread to exit...\n");
	pthread_join(pDecodeThreadData->tDecodeThread, &pDecodeThreadExit);
	if (pDecodeThreadExit != PTHREAD_CANCELED)
	{
		TRACE("Decode thread return value\n");
	}
	else
	{
		TRACE("Decode thread was cancelled.\n");
	}

	/* 
		_TO_DO
		decode data cleanup
	*/
	if (pDecodeThreadData->fp != NULL)
	{
		fclose(pDecodeThreadData->fp);
	}

	return 0;
}